//根据经纬度计算八分体编码
function octantCode(longitude, latitude) {
  let Q;
  if (longitude > 0 && longitude <= 90 && latitude >= 0 && latitude <= 90) {
    Q = 0
  }
  if (longitude > 90 && longitude <= 180 && latitude >= 0 && latitude <= 90) {
    Q = 1
  }
  if (longitude > -180 && longitude <= -90 && latitude >= 0 && latitude <= 90) {
    Q = 2
  }
  if (longitude >= -90 && longitude <= 0 && latitude >= 0 && latitude <= 90) {
    Q = 3
  }
  if (longitude > 0 && longitude <= 90 && latitude >= -90 && latitude <= 0) {
    Q = 4
  }
  if (longitude > 90 && longitude <= 180 && latitude >= -90 && latitude <= 0) {
    Q = 5
  }
  if (longitude > -180 && longitude <= -90 && latitude >= -90 && latitude <= 0) {
    Q = 6
  }
  if (longitude >= -90 && longitude <= 0 && latitude >= -90 && latitude <= 0) {
    Q = 7
  }
  return Q
}
//根据行号求列数
function log2_d(n) {
  let result = 0;
  const cp = n;
  if (n & 0xffff0000) {
    result += 16;
    n >>= 16;
  }
  if (n & 0x0000ff00) {
    result += 8;
    n >>= 8;
  }
  if (n & 0x000000f0) {
    result += 4;
    n >>= 4;
  }
  if (n & 0x0000000c) {
    result += 2;
    n >>= 2;
  }
  if (n & 0x00000002) {
    result += 1;
    n >>= 1;
  }
  if (cp > 1 << result) result++;
  return result;
}

/**
 * 通过全球任一经纬度，拿到该经纬度点对应的所属八分体的行列号及高度
 * @param {Number} level 层次
 * @param {Number} longitude 经度
 * @param {Number} latitude 纬度
 * @param {Number} height 高度
 * @returns 行列号及高度的数组
 */
function rowcoltierNumber(level, longitude, latitude, height = 0) {
  let number = []
  //判断在哪个八分体
  let Q = octantCode(longitude, latitude);
  if (Q == 0) {
    longitude = longitude;
    latitude = latitude
  } else if (Q == 1) {
    longitude = longitude - 90;
    latitude = latitude;
  } else if (Q == 2) {
    longitude = longitude + 180;
    latitude = latitude;
  } else if (Q == 3) {
    longitude = longitude + 90;
    latitude = latitude;
  } else if (Q == 4) {
    longitude = 90 - longitude;
    latitude = -latitude;
  } else if (Q == 5) {
    longitude = 180 - longitude;
    latitude = -latitude;
  } else if (Q == 6) {
    longitude = -longitude - 90;
    latitude = -latitude;
  } else {
    longitude = -longitude;
    latitude = -latitude;
  }
  //计算纬差
  let LDV = 180 / Math.pow(2, level + 1)
  //根据纬差计算行号
  let I = Math.pow(2, level) - 1 - Math.floor(latitude / LDV)

  //根据行号求经差和列号
  //let a = Math.floor(Math.log2(I + 1)) + 1
  //let LOV = 180 / Math.pow(2, a)
  let Jnum = 1 << log2_d(I + 1)
  let LOV = 90 / Jnum
  let J = Math.floor(longitude / LOV)
  //高差
  let HDV = 614400 / Math.pow(2, level)
  //根据高差计算层号
  let H = Math.floor(height * 0.1 / HDV)
  number.push(J, I, H, Q);
  return number
}

const morton256_z = [
  0x00000000n,
  0x00000004n,
  0x00000020n,
  0x00000024n,
  0x00000100n,
  0x00000104n,
  0x00000120n,
  0x00000124n,
  0x00000800n,
  0x00000804n,
  0x00000820n,
  0x00000824n,
  0x00000900n,
  0x00000904n,
  0x00000920n,
  0x00000924n,
  0x00004000n,
  0x00004004n,
  0x00004020n,
  0x00004024n,
  0x00004100n,
  0x00004104n,
  0x00004120n,
  0x00004124n,
  0x00004800n,
  0x00004804n,
  0x00004820n,
  0x00004824n,
  0x00004900n,
  0x00004904n,
  0x00004920n,
  0x00004924n,
  0x00020000n,
  0x00020004n,
  0x00020020n,
  0x00020024n,
  0x00020100n,
  0x00020104n,
  0x00020120n,
  0x00020124n,
  0x00020800n,
  0x00020804n,
  0x00020820n,
  0x00020824n,
  0x00020900n,
  0x00020904n,
  0x00020920n,
  0x00020924n,
  0x00024000n,
  0x00024004n,
  0x00024020n,
  0x00024024n,
  0x00024100n,
  0x00024104n,
  0x00024120n,
  0x00024124n,
  0x00024800n,
  0x00024804n,
  0x00024820n,
  0x00024824n,
  0x00024900n,
  0x00024904n,
  0x00024920n,
  0x00024924n,
  0x00100000n,
  0x00100004n,
  0x00100020n,
  0x00100024n,
  0x00100100n,
  0x00100104n,
  0x00100120n,
  0x00100124n,
  0x00100800n,
  0x00100804n,
  0x00100820n,
  0x00100824n,
  0x00100900n,
  0x00100904n,
  0x00100920n,
  0x00100924n,
  0x00104000n,
  0x00104004n,
  0x00104020n,
  0x00104024n,
  0x00104100n,
  0x00104104n,
  0x00104120n,
  0x00104124n,
  0x00104800n,
  0x00104804n,
  0x00104820n,
  0x00104824n,
  0x00104900n,
  0x00104904n,
  0x00104920n,
  0x00104924n,
  0x00120000n,
  0x00120004n,
  0x00120020n,
  0x00120024n,
  0x00120100n,
  0x00120104n,
  0x00120120n,
  0x00120124n,
  0x00120800n,
  0x00120804n,
  0x00120820n,
  0x00120824n,
  0x00120900n,
  0x00120904n,
  0x00120920n,
  0x00120924n,
  0x00124000n,
  0x00124004n,
  0x00124020n,
  0x00124024n,
  0x00124100n,
  0x00124104n,
  0x00124120n,
  0x00124124n,
  0x00124800n,
  0x00124804n,
  0x00124820n,
  0x00124824n,
  0x00124900n,
  0x00124904n,
  0x00124920n,
  0x00124924n,
  0x00800000n,
  0x00800004n,
  0x00800020n,
  0x00800024n,
  0x00800100n,
  0x00800104n,
  0x00800120n,
  0x00800124n,
  0x00800800n,
  0x00800804n,
  0x00800820n,
  0x00800824n,
  0x00800900n,
  0x00800904n,
  0x00800920n,
  0x00800924n,
  0x00804000n,
  0x00804004n,
  0x00804020n,
  0x00804024n,
  0x00804100n,
  0x00804104n,
  0x00804120n,
  0x00804124n,
  0x00804800n,
  0x00804804n,
  0x00804820n,
  0x00804824n,
  0x00804900n,
  0x00804904n,
  0x00804920n,
  0x00804924n,
  0x00820000n,
  0x00820004n,
  0x00820020n,
  0x00820024n,
  0x00820100n,
  0x00820104n,
  0x00820120n,
  0x00820124n,
  0x00820800n,
  0x00820804n,
  0x00820820n,
  0x00820824n,
  0x00820900n,
  0x00820904n,
  0x00820920n,
  0x00820924n,
  0x00824000n,
  0x00824004n,
  0x00824020n,
  0x00824024n,
  0x00824100n,
  0x00824104n,
  0x00824120n,
  0x00824124n,
  0x00824800n,
  0x00824804n,
  0x00824820n,
  0x00824824n,
  0x00824900n,
  0x00824904n,
  0x00824920n,
  0x00824924n,
  0x00900000n,
  0x00900004n,
  0x00900020n,
  0x00900024n,
  0x00900100n,
  0x00900104n,
  0x00900120n,
  0x00900124n,
  0x00900800n,
  0x00900804n,
  0x00900820n,
  0x00900824n,
  0x00900900n,
  0x00900904n,
  0x00900920n,
  0x00900924n,
  0x00904000n,
  0x00904004n,
  0x00904020n,
  0x00904024n,
  0x00904100n,
  0x00904104n,
  0x00904120n,
  0x00904124n,
  0x00904800n,
  0x00904804n,
  0x00904820n,
  0x00904824n,
  0x00904900n,
  0x00904904n,
  0x00904920n,
  0x00904924n,
  0x00920000n,
  0x00920004n,
  0x00920020n,
  0x00920024n,
  0x00920100n,
  0x00920104n,
  0x00920120n,
  0x00920124n,
  0x00920800n,
  0x00920804n,
  0x00920820n,
  0x00920824n,
  0x00920900n,
  0x00920904n,
  0x00920920n,
  0x00920924n,
  0x00924000n,
  0x00924004n,
  0x00924020n,
  0x00924024n,
  0x00924100n,
  0x00924104n,
  0x00924120n,
  0x00924124n,
  0x00924800n,
  0x00924804n,
  0x00924820n,
  0x00924824n,
  0x00924900n,
  0x00924904n,
  0x00924920n,
  0x00924924n
];


/**
* 三维Morton码
* @param {BigInt} y 行号
* @param {BigInt} x 列号
* @param {BigInt} z 层号
*/
function mortonEncode_LUT_3(y, x, z) {
  let answer = 0n;
  answer =
    (morton256_z[(z & 0xff0000n) >> 16n] << 48n) |
    (morton256_z[(y & 0xff0000n) >> 16n] << 47n) |
    (morton256_z[(x & 0xff0000n) >> 16n] << 46n) |
    (morton256_z[(z & 0x00ff00n) >> 8n] << 24n) |
    (morton256_z[(y & 0x00ff00n) >> 8n] << 23n) |
    (morton256_z[(x & 0x00ff00n) >> 8n] << 22n) |
    morton256_z[z & 0x0000ffn] |
    (morton256_z[y & 0x0000ffn] >> 1n) |
    (morton256_z[x & 0x0000ffn] >> 2n);
  return answer;
};

//给定经纬高度和剖分层次，求三维网格编码
/**
 * 
 * @param {Number} level 剖分等级
 * @param {Number} longitude 经度
 * @param {Number} latitude 纬度
 * @param {Number} height 高度
 * @returns 
 */
function getCode(level, longitude, latitude, height) {
  let code = ''
  let Q = octantCode(longitude, latitude)
  let RCH = rowcoltierNumber(level, longitude, latitude, height)
  let R = BigInt(RCH[0])
  let C = BigInt(RCH[1])
  let H = BigInt(RCH[2])
  let Morton = mortonEncode_LUT_3(R, C, H).toString(8);
  if (Morton.length !== level) {
    Morton = '0'.repeat(level - Morton.length) + Morton;
  }
  code = Q + '' + Morton
  return code
}

/**
 * 
 * @param {Number} C 列号
 * @param {Number} R 行号
 * @param {Number} H 层号
 * @param {Number} Q 八分体号
 * @returns DQG编码
 */
function getCodeFromRCH(C, R, H, Q, level) {
  let code = ''
  C = BigInt(C);
  R = BigInt(R);
  H = BigInt(H);
  let Morton = mortonEncode_LUT_3(R, C, H).toString(8);
  if (Morton.length !== level) {
    Morton = '0'.repeat(level - Morton.length) + Morton;
  }
  code = Q + '' + Morton
  return code
}

/**
 * 莫顿码转行列层号
 * @param {Number} morton 莫顿码 八进制转十进制
 * @return {Object} obj 返回的对象，里面包含了row和col
 */
function mortonToRowColHei(morton) {
  let useless1 = 0n;
  let useless2 = 0n;
  let useless3 = 0n;
  let useless4 = 0n;
  while (morton & 0xffffffffffffffffn) {
    useless1 |= (morton & 1n) << useless4;
    useless2 |= (morton & 2n) << useless4;
    useless3 |= (morton & 4n) << useless4;
    morton >>= 3n;
    ++useless4;
  }
  const obj = {};
  obj.hei = useless3 >> 2n;
  obj.row = useless2 >> 1n;
  obj.col = useless1;
  return obj;
}
//console.log(mortonToRowColHei(3n));

/**
 * 
 * @param {String} code DQG编码组织形式：八分体码+Morton码（八进制）
 * @returns {Object} 包含网格信息的对象，网格中心点经度，网格中心点纬度，网格中心点高度
 */
function getLonLat(code) {
  let LonLatObj = {}
  let level = code.length - 1
  let Q = code.substring(0, 1)
  let morton = BigInt(parseInt(code.substring(1), 8).toString(10))
  let obj = mortonToRowColHei(morton)
  let row = Number(obj.row)
  let col = Number(obj.col)
  let hei = Number(obj.hei)
  //计算纬差
  let LDV = 180 / Math.pow(2, level + 1)
  //根据行号求经差
  let a = 1 << log2_d(row + 1)
  let LOV = 90 / a
  //计算高差
  let HDV = 614400 / Math.pow(2, level)
  LonLatObj.longitude = (col + 0.5) * LOV
  LonLatObj.latitude = 90 - (row + 0.5) * LDV
  LonLatObj.height = (hei + 0.5) * HDV
  //映射到全局
  if(Q == 0){
    LonLatObj.west = col * LOV
    LonLatObj.south = 90 - (row + 1) * LDV
    LonLatObj.east = (col + 1) * LOV
    LonLatObj.north = 90 - row * LDV
    LonLatObj.bottom = hei * HDV
    LonLatObj.top = (hei + 1) * HDV
    LonLatObj.longitude = LonLatObj.longitude
    LonLatObj.latitude = LonLatObj.latitude
    LonLatObj.height = LonLatObj.height
  }
  if(Q == 1) {
    LonLatObj.west = col * LOV + 90
    LonLatObj.south = 90 - (row + 1) * LDV 
    LonLatObj.east = (col + 1) * LOV + 90
    LonLatObj.north = 90 - row * LDV
    LonLatObj.bottom = hei * HDV
    LonLatObj.top = (hei + 1) * HDV
    LonLatObj.longitude = LonLatObj.longitude + 90
    LonLatObj.latitude = LonLatObj.latitude
    LonLatObj.height = LonLatObj.height
  }
  if(Q == 2){
    LonLatObj.west = col * LOV - 180
    LonLatObj.south = 90 - (row + 1) * LDV
    LonLatObj.east = (col + 1) * LOV - 180
    LonLatObj.north = 90 - row * LDV
    LonLatObj.bottom = hei * HDV
    LonLatObj.top = (hei + 1) * HDV
    LonLatObj.longitude = LonLatObj.longitude - 180
    LonLatObj.latitude = LonLatObj.latitude
    LonLatObj.height = LonLatObj.height
  }
  if(Q == 3){
    LonLatObj.west = col * LOV - 90
    LonLatObj.south = 90 - (row + 1) * LDV
    LonLatObj.east = (col + 1) * LOV -90
    LonLatObj.north = 90 - row * LDV
    LonLatObj.bottom = hei * HDV
    LonLatObj.top = (hei + 1) * HDV
    LonLatObj.longitude = LonLatObj.longitude - 90
    LonLatObj.latitude = LonLatObj.latitude
    LonLatObj.height = LonLatObj.height
  }
  if(Q == 4) {
    LonLatObj.west = 90 - (col + 1) * LOV
    LonLatObj.south = -(90 - row * LDV)
    LonLatObj.east = 90 - col * LOV
    LonLatObj.north = -(90 - (row + 1) * LDV)
    if(LonLatObj.north == -0){
      LonLatObj.north = 0
    }
    LonLatObj.bottom = hei * HDV
    LonLatObj.top = (hei + 1) * HDV
    LonLatObj.longitude = 90 - LonLatObj.longitude
    LonLatObj.latitude = -LonLatObj.latitude
    LonLatObj.height = LonLatObj.height
  }
  if(Q == 5){
    LonLatObj.west =  180 - (col + 1) * LOV
    LonLatObj.south = -(90 - row * LDV)
    LonLatObj.east = 180 - col * LOV
    LonLatObj.north = -(90 - (row + 1) * LDV)
    if(LonLatObj.north == -0){
      LonLatObj.north = 0
    }
    LonLatObj.bottom = hei * HDV
    LonLatObj.top = (hei + 1) * HDV
    LonLatObj.longitude = 180 - LonLatObj.longitude
    LonLatObj.latitude = -LonLatObj.latitude
    LonLatObj.height = LonLatObj.height
  }
  if(Q == 6) {
    LonLatObj.west = -((col + 1) * LOV) - 90
    LonLatObj.south = -(90 - row * LDV)
    LonLatObj.east = -(col * LOV) - 90
    LonLatObj.north = -(90 - (row + 1) * LDV)
    if(LonLatObj.north == -0){
      LonLatObj.north = 0
    }
    LonLatObj.bottom = hei * HDV
    LonLatObj.top = (hei + 1) * HDV
    LonLatObj.longitude = -LonLatObj.longitude - 90
    LonLatObj.latitude = -LonLatObj.latitude
    LonLatObj.height = LonLatObj.height
  }
  if(Q == 7){
    LonLatObj.west = -((col + 1) * LOV)
    LonLatObj.south = -(90 - row * LDV)
    LonLatObj.east = -(col * LOV)
    LonLatObj.north = -(90 - (row + 1) * LDV)
    if(LonLatObj.north == -0){
      LonLatObj.north = 0
    }
    LonLatObj.bottom = hei * HDV
    LonLatObj.top = (hei + 1) * HDV
    LonLatObj.longitude = -LonLatObj.longitude
    LonLatObj.latitude = -LonLatObj.latitude
    LonLatObj.height = LonLatObj.height
  }
  return LonLatObj
}


export {
  log2_d,
  getCode,
  getLonLat,
  getCodeFromRCH,
  rowcoltierNumber
}

